"""
A channel that listens on a socket for a single connection. Also useful
for testing. 

If you wanted to make a remote shell using Expect, so that a user could
connect to your computer and interact with your running Python script, it
should be fairly straight forward using this channel.

Copyright (c) 2009 Christopher Hesse
http://code.google.com/p/python-expect/
"""

import socket
import threading
from expect.selectable import Selectable
from expect.exceptions import ChannelNotConnected
from ..socket import SocketChannel
from .acceptthread import AcceptThread


class ListenChannel(object):
    """
    This channel listens on a local port (on all interfaces) for incoming
    connections and accepts the first connection to that port.
    
    The port number can be specified to the __init__ method.  If it is not
    specified, one will be assigned automatically, check the .port property
    after initialization to determine what port the channel is listening on.
    
    You cannot use any channel or expectable methods on this channel before it
    is connected.
    """
    
    def __init__(self, port=0, **kwargs):
        server_socket = socket.socket()
        server_socket.bind(('', port))
        server_socket.listen(1)
        _address, self.port = server_socket.getsockname()
        self.is_connected = False
        self.socket_channel = None
        
        # start a separate thread to listen for a connection
        # the thread exits as soon as someone connects
        self.terminate_selectable = Selectable()
        self.connected_event = threading.Event()
        self.listen_thread = AcceptThread(server_socket, self.terminate_selectable, 
                                          self.connected_event)
        self.listen_thread.start()
        self.kwargs = kwargs
    
    def __del__(self):
        # cause the listen thread to die if it is running
        self.terminate_selectable.set()
        
    def accept_connection(self):
        """
        Accept a pending connection on the ListenChannel, returns true if a
        connection is accepted.
        """
        return self.wait_until_connected(0)
        
    def wait_until_connected(self, timeout=None):
        """
        Wait timeout seconds for a connection to be made to this ListenChannel.
        
        Returns true if a connection is accepted.
        """
        if self.is_connected:
            return True
        
        if timeout is None:
            self.connected_event.wait()
        else:
            self.connected_event.wait(timeout)
        
        self.is_connected = self.connected_event.is_set()
        if self.is_connected:
            self.socket_channel = SocketChannel(socket=self.listen_thread.socket,
                                                **self.kwargs)
        return self.is_connected
        
    def close(self):
        self.terminate_selectable.set()
        if self.is_connected:
            self.socket_channel.close()
            
    def __getattr__(self, name):
        # do not allow access to any methods unless the channel is connected
        if self.is_connected:
            # once the channel is connected, forward all accesses to the wrapped
            # socket channel
            return getattr(self.socket_channel, name)
        else:
            message = "Attempted to call a channel method, but the channel is not yet " \
                      "connected.  You must use .accept_connection() or " \
                      ".wait_until_connected() to connect the channel."
            raise ChannelNotConnected(message)
            
    def __setattr__(self, name, value):
        # forward some setting of attributes to the wrapped class
        if name in ['name', 'buffering']:
            if self.is_connected:
                setattr(self.socket_channel, name, value)
            else:
                raise ChannelNotConnected("Cannot set name before channel is connected")
        else:
            object.__setattr__(self, name, value)